namespace Spectrum {


      using namespace msl;
      

      // These structs simply store submatrix typedefs so that I can have a uniform typedef within SubmatrixPacket and DistributedStructuredMatrix
      
      template< typename T, int Rank, bool Sparsity >
      struct Submatrix_types {};
      
      template< typename T >
      struct Submatrix_types< T, 1, false > {
            typedef Eigen::Matrix< T, Dynamic, 1 > Submatrix;
      };
      
      template< typename T >
      struct Submatrix_types< T, 1, true > {
            typedef Eigen::SparseVector< T > Submatrix;
      };

      template< typename T >
      struct Submatrix_types< T, 2, false > {
            typedef Eigen::Matrix< T, Dynamic, Dynamic > Submatrix;
      };

      template< typename T >
      struct Submatrix_types< T, 2, true > {
            typedef Eigen::SparseMatrix< T > Submatrix;
      };


      // Class for transmission of submatrices between processors

      template< typename T, int Rank, bool Sparsity >
      class SubmatrixPacket : public Serializable {
      
      
            private : 
                  
                  
		      Submatrix_types< T, Rank, Sparsity > Submatrix_type;
		      typedef Submatrix_type::Submatrix Submatrix;       
                  
            
                  // integer vector identifies template parameters and transposition: 0 => T, 1 => rank, 2 => sparsity, 3 => trans
                  // type of T: 1 = short, 2 = int, 3 = float, 4 = double, true = 0, false = 1
                  int parameters [4];
                  // source submatrix id
                  int sourceSubmatrixId;
                  // destination submatrix id
                  int destSubmatrixId;
                  // source processor id
                  int sourceProcId;
                  // destination processor id
                  int destProcId;
                  // max rows
                  int rows;
                  // max cols
                  int cols;
                  // outer index size
                  int outerIdxSize;
                  // inner index size
                  int innerIdxSize;
                  // source global row block
                  int sourceRowBlock;
                  // source global column block
                  int sourceColBlock;
                  // dest global row block
                  int destRowBlock;
                  // dest global column block
                  int destColBlock;
                  // source global row Start
                  int sourceRowStart;
                  // source global column Start
                  int sourceColStart;
                  // dest global row Start
                  int destRowStart;
                  // dest global column Start
                  int destColStart;
                  // dest outer indices - source outer indices
                  int outerOffset;
                  // offset of inner indices
                  int innerOffset
                  // Total size of data to be transmitted
                  int totalSize;
                  // dest outer indices
                  int * outerIdx;
                  // dest inner indices
                  int * innerIdx;
                  // matrix data values
                  T * values;
                  // chunk boundaries (may not be filled)
                  int [4] chunk;
                  
            
            public : 
            
                  SubmatrixPacket()
                  
                  {}
                  
            
                  ~SubmatrixPacket() {
                  
                        delete [] outerIdx;
                        delete [] innerIdx;
                        delete [] values;
                        
                  }
                  
                  
                  // Fills all data from given submatrix
                  init(Submatrix * host, int submatrixId, int procId, int i0, int j0, int rb, int cb, int oo, int ii) {
                  
                        switch (boost::TYPE_OF_TPL(T)) {
                              case short : parameters[0] = 1; break;
                              case int : parameters[0] = 2; break;
                              case float : parameters[0] = 3; break
                              case double : parameters[0] = 4; break;
                        }
                        
                        parameters[1] = Rank;
                        parameters[2] = (int) Sparsity;
                        parameters[3] = (int) host->getTranspose();
                        parameters[4] = 1;
                        
                        sourceSubmatrixId = host->submatrixId;
                        destSubmatrixId = submatrixId;
                        sourceProcId = host->procId;
                        destProcId = procId;
                        rows = host->rows();
                        cols = host->cols();
                        sourceRowBlock = host->getRowBlock();
                        sourceColBlock = host->getColumnBlock();
                        destRowBlock = rb;
                        destColBlock = cb;
                        sourceRowStart = host->global_starts_.first;
                        sourceColStart = host->global_starts_.second;
                        destRowStart = i0;
                        destColStart = j0;
                        outerIdxSize = host->getPacketOuterIdxSize();
                        innerIdxSize = host->getPacketInnerIdxSize();
                        outerOffset = oo;
                        innerOffset = ii;
                        totalSize = (sizeof(int) * (27 + outerIdxSize + innerIdxSize)) + (sizeof(T) * innerIdxSize);
                        outerIdx = host->getPacketOuterIdx();
                        innerIdx = host->getPacketInnerIdx();
                        values = host->getPacketValues();
                        chunk[0] = -1;
                        chunk[1] = -1;
                        chunk[2] = -1;
                        chunk[3] = -1;
                                          
                  }
      
      
                  // Fills partial data from given submatrix defined by chunk
                  init(Submatrix * host, int submatrixId, int procId, int i0, int j0, int rb, int cb, int oo, int ii, int [4] c) {
                  
                        switch (boost::TYPE_OF_TPL(T)) {
                              case short : parameters[0] = 1; break;
                              case int : parameters[0] = 2; break;
                              case float : parameters[0] = 3; break
                              case double : parameters[0] = 4; break;
                        }
                        
                        parameters[1] = Rank;
                        parameters[2] = (int) Sparsity;
                        parameters[3] = (int) host->getTranspose();
                        parameters[4] = 0;
                        
                        sourceSubmatrixId = host->submatrixId;
                        destSubmatrixId = submatrixId;
                        sourceProcId = host->procId;
                        destProcId = procId;
                        rows = host->rows();
                        cols = host->cols();
                        sourceRowBlock = host->getRowBlock();
                        sourceColBlock = host->getColumnBlock();
                        destRowBlock = rb;
                        destColBlock = cb;
                        sourceRowStart = host->global_starts_.first;
                        sourceColStart = host->global_starts_.second;
                        destRowStart = i0;
                        destColStart = j0;
                        outerIdxSize = host->getPacketOuterIdxSize(c);
                        innerIdxSize = host->getPacketInnerIdxSize(c);
                        outerOffset = oo;
                        innerOffset = ii;
                        totalSize = (sizeof(int) * (27 + outerIdxSize + innerIdxSize)) + (sizeof(T) * innerIdxSize);
                        outerIdx = host->getPacketOuterIdx(c);
                        innerIdx = host->getPacketInnerIdx(c);
                        values = host->getPacketValues(c);
                        chunk[0] = c[0];
                        chunk[1] = c[1];
                        chunk[2] = c[2];
                        chunk[3] = c[3];
                                                                  
                  }
                                          
                  
			int getSize() {
				return totalSize;
			}


			void reduce(void* buffer, int bufferSize) {
				
				int index = 0;
				
				for (int param = 0; param < 5; ++param) {
				      msl::write(buffer, parameters[param], index);
				      index += msl::SOI;
				}
				
				msl::write(buffer, destSubmatrixId, index);
				index += msl::SOI;
				msl::write(buffer, sourceProcId, index);
				index += msl::SOI;
				msl::write(buffer, sourceProcId, index);
				index += msl::SOI;
				msl::write(buffer, rows, index);
				index += msl::SOI;
				msl::write(buffer, cols, index);
				index += msl::SOI;
				msl::write(buffer, sourceRowBlock, index);
				index += msl::SOI;
				msl::write(buffer, sourceColBlock, index);
				index += msl::SOI;
				msl::write(buffer, destRowBlock, index);
				index += msl::SOI;
				msl::write(buffer, destColBlock, index);
				index += msl::SOI;
				msl::write(buffer, sourceRowStart, index);
				index += msl::SOI;
				msl::write(buffer, sourceColStart, index);
				index += msl::SOI;
				msl::write(buffer, destRowStart, index);
				index += msl::SOI;
				msl::write(buffer, destColStart, index);
				index += msl::SOI;
				msl::write(buffer, outerIdxSize, index);
				index += msl::SOI;
				msl::write(buffer, innerIdxSize, index);
				index += msl::SOI;
				msl::write(buffer, outerOffset, index);
				index += msl::SOI;
				msl::write(buffer, innerOffset, index);
				index += msl::SOI;
				msl::write(buffer, totalSize, index);
				index += msl::SOI;

				for (int outer = 0; outer < outerIdxSize; ++outer) {
				      msl::write(buffer, outerIdx[outer], index);
				      index += msl::SOI;
				}

				for (int inner = 0; inner < innerIdxSize; ++inner) {
				      msl::write(buffer, innerIdx[inner], index);
				      index += msl::SOI;
				}
				
                        for (int inner = 0; inner < innerIdxSize; ++inner) {
                              msl::write(buffer, values[inner], index);
                              index += sizeof(T);
                        }
                        
                        for (int c = 0; c < 4; ++c) {
                              msl::write(buffer, chunk[c], index);
                              index += msl::SOI;
                        }
				
			}

			void expand(void* buffer, int bufferSize) {
				
				int index = 0;
				
				for (int param = 0; param < 5; ++param) {
				      parameters[param] = msl::read<int>(buffer, index);
				      index += msl::SOI;
				}
				
				destSubmatrixId = msl::read<int>(buffer, index);
				index += msl::SOI;
				sourceProcId = msl::read<int>(buffer, index);
				index += msl::SOI;
				sourceProcId = msl::read<int>(buffer, index);
				index += msl::SOI;
				rows = msl::read<int>(buffer, index);
				index += msl::SOI;
				cols = msl::read<int>(buffer, index);
				index += msl::SOI;
				sourceRowBlock = msl::read<int>(buffer, index);
				index += msl::SOI;
				sourceColBlock = msl::read<int>(buffer, index);
				index += msl::SOI;
				destRowBlock = msl::read<int>(buffer, index);
				index += msl::SOI;
				destColBlock = msl::read<int>(buffer, index);
				index += msl::SOI;
				sourceRowStart = msl::read<int>(buffer, index);
				index += msl::SOI;
				sourceColStart = msl::read<int>(buffer, index);
				index += msl::SOI;
				destRowStart = msl::read<int>(buffer, index);
				index += msl::SOI;
				destColStart = msl::read<int>(buffer, index);
				index += msl::SOI;
				outerIdxSize = msl::read<int>(buffer, index);
				index += msl::SOI;
				innerIdxSize = msl::read<int>(buffer, index);
				index += msl::SOI;
				outerOffset = msl::read<int>(buffer, index);
				index += msl::SOI;
				innerOffset = msl::read<int>(buffer, index);
				index += msl::SOI;
				totalSize = msl::read<int>(buffer, index);
				index += msl::SOI;
				
				outerIdx = new int [outerIdxSize];
				innerIdx = new int [innerIdxSize];
				values = new T [innerIdxSize];

				for (int outer = 0; outer < outerIdxSize; ++outer) {
				      outerIdx[outer] = msl::read<int>(buffer, index);
				      index += msl::SOI;
				}

				for (int inner = 0; inner < innerIdxSize; ++inner) {
				      innerIdx[inner] = msl::read<int>(buffer, index);
				      index += msl::SOI;
				}
				
                        for (int inner = 0; inner < innerIdxSize; ++inner) {
                              values[inner] = msl::read<T>(buffer, index);
                              index += sizeof(T);
                        }
                        
                        for (int c = 0; c < 4; ++c) {
                              chunk[c] = msl::read<int>(buffer, index);
                              index += msl::SOI;
                        }
				
			}
			
			
			void buildSubmatrix(Submatrix * destination) {
			
			      destination->initialize(this->destSubmatrixId, this->destProcId, this->rows, this->cols,
			            this->destRowStart, this->destColStart, this->destRowBlock, this->destColBlock,
			            this->outerIdx, this->innerIdx, this->values);
			
			}
			
			
			void contributeToSubmatrix(Submatrix * destination) {
			
			      destination->fillInChunk(this->outerIdx, this->innerIdx, this->values);
			      
			}
      
      };
      
                  
